/**
 * 
 */
package register;

import java.io.IOException;
//import java.util.BitSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

//import FilesManagers.FileManager;
import bplusfile.BPlusTreeFile;
//import bplusfile.ByteBitSet;
//import bplustree.BPlusTree;


public class Register {

	private LinkedList<Integer> atributes;
	private RegisterID ID;
		
	
	/**
	 * @return the iD
	 */
	public RegisterID getID() {
		return ID;
	}

	/**
	 * @param id the iD to set
	 */
	public void setID(RegisterID id) {
		ID = id;
	}

	/**
	 * 
	 */
	public Register() {
		atributes=new LinkedList<Integer>();
		ID=new RegisterID();
		
	
	}
	
	public Register(RegisterID id){
		atributes=new LinkedList<Integer>();
		ID=id;
		
		
	}
	
	public Register(RegisterID id,int i){
		atributes=new LinkedList<Integer>();
		ID=id;
		this.addAtribute(i);
		
		
	}
	public void setAtribute(int i){
		atributes=new LinkedList<Integer>();
		this.addAtribute(i);		
	}
	public Integer getFirstAtribute(){
		if(!this.atributes.isEmpty()) return this.atributes.get(0);
		return -1;
	}
	
	/**
	 * Constructor para persistencia, se le carga el register directamente como viene del 
	 * TODO DEBERIA ARROJAR ALGUNA EXCEPCION SI EL ARCHIVO ESTA MAL
	 * @param str
	 */
	public void load(String str){
		String[] aux=str.split(getSeparatorID());
		ID.setID(aux[0]);
		
		if(aux.length==2){ //esto pasa solo en las hojas
			String[] aux2=aux[1].split(getSeparatorAttribute());
			for(int i=0; i<aux2.length; i++){
				atributes.addLast(Integer.valueOf(aux2[i]));
			}
		}
		
	}
	
	
	
	/**
	 * ESPERA RECIBIR EL ARCHIVO AL COMIENZO DEL CAMPO y LO DEVUELVE EN EL BYTE SIGUIENTE AL ULTIMO
	 * @param file
	 * @throws IOException 
	 */
	public void loadGamma(BPlusTreeFile file) throws IOException{
		String aux=file.readUntil(getSeparatorID().charAt(0));
		ID.setID(aux);
		
		int atributos=Integer.parseInt(file.readUntil(getSeparatorID().charAt(0)));
		int contador=8; //empiezo con uno mas porque se empieza restando
		
		int numero=0;
		int i;int bits;
		LinkedList<Integer> atributesDif = new LinkedList<Integer>();
			//////////////////////// ahora tengo que leer de a uno los registros gammacoded y restarle al contador
		
		StringBuffer binario= new StringBuffer();
		int letra = file.read();
		String letraBinario = Integer.toBinaryString(letra);
		for(int y=0;y<(8-letraBinario.length());y++)binario.append("0");//+="0";
		binario.append(letraBinario);//+=Integer.toBinaryString(letra);
		if(binario.length()>8)System.out.println("Sacaste mal el binario");
		
		//-FileManager.append("GammaLog.txt"," lei "+bbs.toSuperString()+" y con el orig "+bs.toString()+"\n");
		while(atributos>0){
			atributos--;
			bits=0;
			numero=0;
			while(true){
				contador--;
				if(contador==-1){
					binario= new StringBuffer();//="";
					letra = file.read();
					letraBinario = Integer.toBinaryString(letra);
					for(int y=0;y<(8-letraBinario.length());y++)binario.append("0");//+="0";
					binario.append(letraBinario);//+=Integer.toBinaryString(letra);
					contador=7;
				}
				
				if(binario.charAt(7-contador)=='0'){
					bits++;
				}else{					
					break;				
				}
			}
			//ahora empieza a leer con bits
			
			if(bits>0){
				i=(int) Math.pow(2,bits);
			}else{
				i=0;
			}
			//FileManager.append("treeLog.txt"," 2^ "+bits+" ");
			numero=i; //empieza con 2 a la numero de bits que es la primer potencia que es la que saque
			int posicionBit =bits-1;
			int acum=0;
			
			for(int y=0;y<bits;y++){
				contador--;
				
				if(contador==-1){
					binario= new StringBuffer();//="";
					letra = file.read();
					letraBinario = Integer.toBinaryString(letra);
					for(int w=0;w<(8-letraBinario.length());w++)binario.append("0");//+="0";
					binario.append(letraBinario);//+=Integer.toBinaryString(letra);
					contador=7;
				}
				
				if(binario.charAt(7-contador)=='1'){
					acum+=(int)Math.pow(2,posicionBit);					
				}
				posicionBit--;
			}
			if(bits>0){
				numero+= acum; 
			}else{
				numero=1;				
			}
			atributesDif.addLast(numero);numero=0;acum=0;
		}
		
		atributes=convertToAbsolutes(atributesDif);		
	}
	
	public void merge(Register reg){
		Iterator<Integer> iterator = reg.atributes.iterator();
		
		while(iterator.hasNext()){ //addAtribute ya lo ordena
			this.addAtribute(iterator.next());
		}
		
	}
	
	public void addAtribute(int i) {
		
		if(atributes.isEmpty()){
			atributes.add(i);
			return;
		}
		
		int j=-1;
		int actual;
		ListIterator<Integer> iter= atributes.listIterator();
		while(iter.hasNext()) {
			j++;
			actual=iter.next();
			if(actual==i) return;
			if(actual>i){
				iter.previous(); //para compensar por el que me pase
				iter.add(i); 
				return;
			}
		}
		atributes.addLast(i);
	}

	public int getSize(){
		//if(atributes.isEmpty()) return 0;
		//return tamanioNuevo;
		return writeGamma().length();
		//return write().length();
	}

	public String toStringList() {
		String output="";
		output=output+getSize()+"; "+ID.getID()+"||";
				
		Iterator iter= atributes.iterator();
		
		while(iter.hasNext()) output=output+iter.next().toString()+"|";  // sumo 1 para el final de campo
		return output;
	}
	
	/**
	 * Persistence string
	 */
	
	public String write() {
		StringBuffer output=new StringBuffer(ID.getID()+getSeparatorID());
			
		Iterator<Integer> iter= atributes.iterator();
		
		while(iter.hasNext()) output.append(iter.next().toString()+getSeparatorAttribute());
		return output.toString();
	}
	
	/**
	 * Persistence string gamma coded
	 * second field is number of atributes, must replace final separator because of coding
	 * @return
	 */
	public String writeGamma(){

		StringBuffer output=new StringBuffer(ID.getID()+getSeparatorID());
		//FileManager.append("GammaLog.txt","WRITE "+ID.getID()+"  ");
		
		output.append(Integer.toString(atributes.size()) + getSeparatorID()); 
		//FileManager.append("treeLog.txt","\n WRITE "+output.toString()+" ");		
		LinkedList<Integer> atributesDif = convertToDiferences(atributes);
		
		Iterator<Integer> iter= atributesDif.iterator();
		/*
		Iterator<Integer> iterdebug= atributesDif.iterator();
		while(iterdebug.hasNext()){
			System.out.println("Atributo diferencia = " + iterdebug.next());
		}
		*/
		
		StringBuffer tempOut=new StringBuffer();
		int present,i,j,contador=7;
		StringBuffer gamma=new StringBuffer();//"";
		while(iter.hasNext()){
			present=iter.next();
			
			int orig=present;
			for(i=-2,j=1;j<=present;j*=2,i++);//i va a ser el numero de ceros y 2*i+1 el total de bits
			j/=2;	//ajuste porque en realidad me pase
			//ajuste porque en realidad me pase en i va a ajustar solo
			present-=j; //esto es lo que queda por representar en binario
			
			//Inserto los ceros correspondientes
			int cantCeros = (int)(Math.log(orig) / Math.log(2));
			for(int k=0; k<cantCeros;k++){
				gamma.append("0");//gamma+="0";		
				contador--;
				if(contador==-1){//si ya hizo un byte, lo escribe
					contador=7;
					tempOut.append((char)Integer.valueOf(gamma.toString(), 2).intValue()/*bset.toString()*/);
					gamma= new StringBuffer();//gamma="";					
				}
			}
			
			//Ahora inserto el 1 correspondiente al gamma
			gamma.append("1");//gamma+="1";
			contador--;
			if(contador==-1){//si ya hizo un byte, lo escribe
				contador=7;
				tempOut.append((char)Integer.valueOf(gamma.toString(), 2).intValue()/*bset.toString()*/);
				gamma= new StringBuffer();//gamma="";
				
			}
			String binarioString = Integer.toBinaryString(present);//this.obtenerBinario(present,true);
			if(!binarioString.equals("0") || cantCeros!=0 ){
				//Pongo lo ceros necesario despues del 1 para tener la misma cantidad de binarios antes y despues
				int restantes =cantCeros;
				for(int y=0; y<(restantes-binarioString.length());y++){
					gamma.append("0");//gamma+="0";		
					contador--;
					if(contador==-1){//si ya hizo un byte, lo escribe
						contador=7;
						tempOut.append((char)Integer.valueOf(gamma.toString(), 2).intValue()/*bset.toString()*/);
						gamma= new StringBuffer();//gamma="";
						
					}
				}
				for(int y=0; y<binarioString.length();y++){
					if(binarioString.charAt(y)=='1'){
						gamma.append("1");//gamma+="1";
					}else{
						gamma.append("0");//gamma+="0";					
					}
					
					contador--;
					if(contador==-1){//si ya hizo un byte, lo escribe
						contador=7;
						tempOut.append((char)Integer.valueOf(gamma.toString(), 2).intValue()/*bset.toString()*/);
						gamma= new StringBuffer();//gamma="";
					}
					
				}
			}
							
		}
		
		// ahora tengo que completar el ultimo byte para que escriba bien. No importa como termina
		if(contador<7){
			
			for(int h=contador;h>=0;h--) {
				gamma.append("1");//gamma+="1";
			}
			//gamma=gamma.substring(0, gamma.length()-1)+"0";
			tempOut.append((char)Integer.valueOf(gamma.toString(), 2).intValue()/*bset.toString()*/);
			gamma= new StringBuffer();//gamma="";			
		}
		
		
		return output.append(tempOut).toString();		
		
	}

	private LinkedList<Integer> convertToDiferences(LinkedList<Integer> atributes2) {
		//System.out.println("Convirtiendo a Diferencia");
		LinkedList<Integer> list=new LinkedList<Integer>();
		if(atributes2.isEmpty()) return list;
		ListIterator<Integer> iter2=atributes2.listIterator();
		//si no tenia nada ya salio, o sea que al menos uno hay
		int prevAbs,currAbs;
		prevAbs=0;
		while(iter2.hasNext()){
			currAbs=iter2.next();
			//System.out.println("absoluto = " + currAbs);
			//System.out.println("diferencia = " + (currAbs-prevAbs));
			list.addLast(currAbs-prevAbs);
			prevAbs=currAbs;
		}
		return list;
		
	}
	
	private LinkedList<Integer> convertToAbsolutes(LinkedList<Integer> atributes2) {
		//System.out.println("Convirtiendo a Absoluto");
		LinkedList<Integer> list=new LinkedList<Integer>();
		if(atributes2.isEmpty()) return list;
		ListIterator<Integer> iter2=atributes2.listIterator();
		//si no tenia nada ya salio, o sea que al menos uno hay
		int prev,act;
		prev=0;
		while(iter2.hasNext()){
			act=iter2.next();
			prev+=act;
			//System.out.println("diferencia = " + act);
			//System.out.println("absoluto = " + prev);
			
			list.addLast(prev);
		}
		return list;
		
	}

	private String getSeparatorAttribute() {
		return "_";
	}

	private String getSeparatorID() {
		return "#";
	}

	
	public int getAtributtesSize(){
		return this.atributes.size();
	}

}
